local famBags = LibStub('AceAddon-3.0'):NewAddon('famBags', 'AceEvent-3.0', 'AceConsole-3.0', 'AceHook-3.0')
local L = LibStub('AceLocale-3.0'):GetLocale('famBags')
local LSM = LibStub('LibSharedMedia-3.0')
local lbf = LibStub("LibButtonFacade", true)

famBags.CURRENT_VERSION = 010209
famBags.FIRST_BUILD = true

famBags.L = L
famBags.SortedCategories = {
   ['all'] = {},
   ['bags'] = {},
   ['bank'] = {},
   ['byName'] = {},
}
famBags.BagIndexes = {}
famBags.Inventory = {}
famBags.IsBankOpen = nil
famBags.ForceCurrentPlayerChange = {}
famBags.bagsSearchFilter = nil
famBags.bankSearchFilter = nil
famBags.CHANGING_PROFILE = nil
famBags.BAGS_NEED_LAYOUT = nil
famBags.BANK_NEED_LAYOUT = nil
famBags.EMPTIES_NEED_LAYOUT = {
   bags = true,
   bank = true,
}
famBags.BagTypes = {
   [0] = 'any',
   [1] = 'quiver',
   [2] = 'ammo',
   [4] = 'soul',
   [8] = 'leather',
   [16] = 'inscription',
   [32] = 'herbs',
   [64] = 'enchanting',
   [128] = 'engineering',
   [256] = 'key',
   [512] = 'gem',
   [1024] = 'mining',
}
famBags.OPTIMIZATION = {
   inProgress = false,
   index = 0,
   subIndex = 0,
   bag = '',
   set = {}
}
famBags.OFFLINE_ENABLED = nil
famBags.PLAYER_NAME = nil
famBags.CURRENT_PLAYER = {}
famBags.MAINFRAMES = {}
famBags.CHARACTERS = {}

-- timer for OnUpdate execution control
famBags.timerFrame = CreateFrame('Frame', 'famBagsTimerFrame', UIParent)
famBags.timerFrame:Hide()
famBags.timer = 0
famBags.ticktime = 1
famBags.timerFrame:SetScript("OnUpdate",function(self, elapsed)
   famBags.timer = famBags.timer + elapsed
   if famBags.timer >= famBags.ticktime then
      famBags:TimerTick()
      famBags.timer = 0
   end
end)

-- :::::::::::: GLOBALS ::::::::::::: -------------------------------------------------------------------------

FAMBAGSCRITERIA_TYPESUBTYPE = 1
FAMBAGSCRITERIA_QUALITY = 2
FAMBAGSCRITERIA_EQUIPSLOT = 3
FAMBAGSCRITERIA_STATS = 4
FAMBAGSCRITERIA_BOUNDSTATUS = 5
FAMBAGSCRITERIA_EQUIPSET = 6
FAMBAGSCRITERIA_OUTFITTERSET = 7
FAMBAGSCRITERIA_QUEST = 8
FAMBAGSCRITERIA_KEY = 9
FAMBAGSCRITERIA_EMPTY = 10
FAMBAGSCRITERIA_USABLE = 11
FAMBAGSCRITERIA_NEW = 12
FAMBAGSCRITERIA_INCREMENTED = 13
FAMBAGSCRITERIA_OTHERS = 14
FAMBAGSCRITERIA_CATEGORIES = 15
FAMBAGSCRITERIA_NAME = 16
FAMBAGSCRITERIA_SPELL = 17
FAMBAGSCRITERIA_ITEMLEVEL = 18
FAMBAGSCRITERIA_REQUIREDLEVEL = 19

-- :::::::::::: famBags.Categories ::::::::::::: -------------------------------------------------------------------------

famBags.Criterias = {
   [ 1] = { name = L['ORDER_TYPE']..'/'..L['ORDER_SUBTYPE'],
            options = { }, },
   [ 2] = { name = L['Quality'],
            options = {L['unknown'], ITEM_QUALITY0_DESC, ITEM_QUALITY1_DESC, ITEM_QUALITY2_DESC, ITEM_QUALITY3_DESC, ITEM_QUALITY4_DESC, ITEM_QUALITY5_DESC,
                       ITEM_QUALITY6_DESC, ITEM_QUALITY7_DESC, } },
   [ 3] = { name = L['Equipment Slot'],
            options = {INVTYPE_AMMO, INVTYPE_HEAD, INVTYPE_NECK, INVTYPE_SHOULDER, INVTYPE_BODY, INVTYPE_CHEST, INVTYPE_ROBE, INVTYPE_WAIST, INVTYPE_LEGS, INVTYPE_FEET,
                       INVTYPE_WRIST, INVTYPE_HAND, INVTYPE_FINGER, INVTYPE_TRINKET, INVTYPE_CLOAK, INVTYPE_WEAPON, INVTYPE_SHIELD, INVTYPE_2HWEAPON,
                       INVTYPE_WEAPONMAINHAND, INVTYPE_WEAPONOFFHAND, INVTYPE_HOLDABLE, INVTYPE_RANGED, INVTYPE_THROWN, INVTYPE_RANGEDRIGHT, INVTYPE_RELIC, INVTYPE_TABARD }, },
   [ 4] = { name = L['Stats'],
            stringKeys = true,
            selfKeys = true,
            options = { ['ITEM_MOD_AGILITY_SHORT'] = ITEM_MOD_AGILITY_SHORT,
                        ['ITEM_MOD_ATTACK_POWER_SHORT'] = ITEM_MOD_ATTACK_POWER_SHORT,
                        ['ITEM_MOD_BLOCK_RATING_SHORT'] = ITEM_MOD_BLOCK_RATING_SHORT,
                        ['ITEM_MOD_CRIT_RATING_SHORT'] = ITEM_MOD_CRIT_RATING_SHORT,
                        ['ITEM_MOD_DODGE_RATING_SHORT'] = ITEM_MOD_DODGE_RATING_SHORT,
                        ['ITEM_MOD_EXPERTISE_RATING_SHORT'] = ITEM_MOD_EXPERTISE_RATING_SHORT,
                        ['ITEM_MOD_HASTE_RATING_SHORT'] = ITEM_MOD_HASTE_RATING_SHORT,
                        ['ITEM_MOD_HIT_RATING_SHORT'] = ITEM_MOD_HIT_RATING_SHORT,
                        ['ITEM_MOD_INTELLECT_SHORT'] = ITEM_MOD_INTELLECT_SHORT,
                        ['ITEM_MOD_MASTERY_RATING_SHORT'] = ITEM_MOD_MASTERY_RATING_SHORT,
                        ['ITEM_MOD_PARRY_RATING_SHORT'] = ITEM_MOD_PARRY_RATING_SHORT,
                        ['ITEM_MOD_RESILIENCE_RATING_SHORT'] = ITEM_MOD_RESILIENCE_RATING_SHORT,
                        ['ITEM_MOD_SPELL_PENETRATION_SHORT'] = ITEM_MOD_SPELL_PENETRATION_SHORT,
                        ['ITEM_MOD_SPELL_POWER_SHORT'] = ITEM_MOD_SPELL_POWER_SHORT,
                        ['ITEM_MOD_SPIRIT_SHORT'] = ITEM_MOD_SPIRIT_SHORT,
                        ['ITEM_MOD_STAMINA_SHORT'] = ITEM_MOD_STAMINA_SHORT,
                        ['ITEM_MOD_STRENGTH_SHORT'] = ITEM_MOD_STRENGTH_SHORT,
                        ['RESISTANCE0_NAME'] = RESISTANCE0_NAME,
                        ['RESISTANCE1_NAME'] = RESISTANCE1_NAME,
                        ['RESISTANCE2_NAME'] = RESISTANCE2_NAME,
                        ['RESISTANCE3_NAME'] = RESISTANCE3_NAME,
                        ['RESISTANCE4_NAME'] = RESISTANCE4_NAME,
                        ['RESISTANCE5_NAME'] = RESISTANCE5_NAME,
                        ['RESISTANCE6_NAME'] = RESISTANCE6_NAME, },
   },
   [ 5] = { name = L['Bound Status'],
            options = {L['Binds when picked up'], L['Binds when equipped'], L['Soulbound'], L['Binds to account'], L['Account Bound']}, },
   [ 6] = { name = L['Equipment Set'], stringKeys = true, options = {}, },
   [ 7] = { name = L['Outfitter Set'], stringKeys = true, options = {}, },
   [ 8] = { name = L['Quest'], options = {}, },
   [ 9] = { name = L['Key'], options = {}, },
   [10] = { name = L['Empty'], options = {}, },
   [11] = { name = L['Usable'], options = {}, },
   [12] = { name = L['New'], options = {}, },
   [13] = { name = L['Incremented'], options = {}, },
   [14] = { name = L['Others'], options = {}, },
   [15] = { name = L['Categories'],
            noAll = true,
            stringKeys = true,
            options = famBags.SortedCategories.all },
   [16] = { name = L['Name'],
            options = {L['Equal'], L['Contains'], L['Starts with'], L['Ends with']},
            valueType = 'text', },
   [17] = { name = L['Spell'],
            options = {L['Equal'], L['Contains'], L['Starts with'], L['Ends with']},
            valueType = 'text', },
   [18] = { name = L['Item Level'],
            options = {L['Equal'], L['Greater or equal'], L['Less or equal'], L['Between']},
            valueType = 'text', },
   [19] = { name = L['Required Level'],
            options = {L['Equal'], L['Greater or equal'], L['Less or equal'], L['Between']},
            valueType = 'text', },
}


-- Build types & subtypes
famBags.Subtypes = {}

local function build_type_table(...)
   local arr = {}
   --print(select(1, ...))
   for i=1, select("#", ...) do
      local a = select(i, ...)
      arr[i] = a;
   end
   return arr
end

famBags.Criterias[1].options = build_type_table(GetAuctionItemClasses())
for i, t in ipairs(famBags.Criterias[1].options) do
   famBags.Subtypes[i] = build_type_table(GetAuctionItemSubClasses(i))
end

famBags.Symbols = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'}


-- :::::::::::: famBags.Orders ::::::::::::: -------------------------------------------------------------------------

famBags.Orders = {
   ['ORDER_EMPTY'] = { 'ORDER_GO_FIRST', 'ORDER_GO_LAST' },
   ['ORDER_ITEM_ID'] = { 'ORDER_ASCENDING', 'ORDER_DESCENDING' },
   ['ORDER_QUALITY'] = { 'ORDER_ASCENDING', 'ORDER_DESCENDING' },
   ['ORDER_QUEST_ITEM'] = { 'ORDER_GO_FIRST', 'ORDER_GO_LAST' },
   ['ORDER_TYPE'] = { 'ORDER_ASCENDING', 'ORDER_DESCENDING' },
   ['ORDER_SUBTYPE'] = { 'ORDER_ASCENDING', 'ORDER_DESCENDING' },
   ['ORDER_ILEVEL'] = { 'ORDER_ASCENDING', 'ORDER_DESCENDING' },
   ['ORDER_RLEVEL'] = { 'ORDER_ASCENDING', 'ORDER_DESCENDING' },
   ['ORDER_STACK_SIZE'] = { 'ORDER_ASCENDING', 'ORDER_DESCENDING' },
   ['ORDER_COUNT'] = { 'ORDER_ASCENDING', 'ORDER_DESCENDING' },
   ['ORDER_EQUIPSLOT'] = { 'ORDER_ASCENDING', 'ORDER_DESCENDING' },
}

-- :::::::::::: DB ::::::::::::: -------------------------------------------------------------------------

local db

function famBags:GetBGDefaults()
   return {
      type = 1, -- Solid
      color1 = {0,0,0,0.7},
      color2 = {0,0,0,0.7},
      texture = nil,
      inset = 0,
      br_texture = 'None',
      br_color = {1,1,1,1},
      br_size = 10,
   }
end

function famBags:GetTextDefaults()
   return {
      font = '',
      size = 11,
         flags = {
         ['OUTLINE'] = false,
         ['THICKOUTLINE'] = false,
         ['MONOCHROME'] = false,
         ['SHADOW'] = true,
      },
      color = {1,1,1},
      halign = 'LEFT',
      valign = 'CENTER',
      padding = 1,
   }
end

function famBags:GetTitleDefaults()
   return {
      is_inset = false,
      inset = 0,
      bg = self:GetBGDefaults(),
      text = self:GetTextDefaults()
   }
end

local defaults = {
   global = {
      version = nil,
      offline = {
         enabled = false,
         buttons = {
         },
      },
   },
   profile =  {
      stackmode = true,
      bags_searchmode = false,
      bank_searchmode = false,
      bags_altsmode = false,
      bank_altsmode = false,
      layout = {
         skins = {
            bags = {},
         },
         general ={
            strata = 'HIGH',
            sections = 'categories',
            show_keys = true,
            full_layout = false,
            update_time = 0.7,
            info = false,
         },
         sections = {
            compact = true,
            hpadding = 5,
            vpadding = 5,
            hspacing = 5,
            vspacing = 5,
            tooltip = true,
            tooltip_vendor = true,
         },
         buttons = {
            hpadding = 5,
            vpadding = 5,
            hspacing = 5,
            vspacing = 5,
            size = 0.7,
            quality_glow = 'round1',
            quality_glow_alpha = 1,
            quality_glow_inset = 0,
            indicators_enabled = true,
            indicators_size = 1,
            indicators_offset = 2,
         },
         stacks = {
            only_stackable = false,
            only_full = false,
         },
         counts = famBags:GetTextDefaults(),
         empties = {
            bg = famBags:GetBGDefaults(),
            hpadding = 5,
            vpadding = 5,
            vpos = 'TOP',
            hpos = 'RIGHT',
            no_empty_text = 'x',
            no_empty_color = {1,0,0},
         },
         order = {
            ordered = true,
            default = true,
            customs = {},
         },
         actions = {
            stack = true,
            refresh = true,
            bagswindow = true,
            resetupdated = true,
            search = true,
            optimize = true,
            options = true,
            alts = true,
            categories = true,
         }
      },
      mainwindow = {
         layout = {
            buttons_x_row = 10,
            halign = 'RIGHT',
            valign = 'TOP',
            hspacing = 40,
            vspacing = 40,
            persist_pos = true,
            fixed_width = true,
         },
         bg = famBags:GetBGDefaults(),
         title = famBags:GetTitleDefaults(),
         search = {
            hpadding = 0,
            vpadding = 0,
            width = 0.3,
            level = 0,
            animation = true,
            bg = famBags:GetBGDefaults(),
         },
         alts = {
            hpadding = 0,
            vpadding = 0,
            width = 0.3,
            level = 0,
            animation = true,
            bg = famBags:GetBGDefaults(),
         },
      },
      money = {
         enabled = true,
         show_silver = true,
         show_copper = true,
         bg = famBags:GetBGDefaults(),
         text = famBags:GetTextDefaults(),
      },
      bank = {
         layout = {
            buttons_x_row = 15,
            halign = 'LEFT',
            valign = 'TOP',
            hspacing = 40,
            vspacing = 40,
            persist_pos = true,
            fixed_width = true,
         },
         bg = famBags:GetBGDefaults(),
         title = famBags:GetTitleDefaults(),
         search = {
            hpadding = 0,
            vpadding = 0,
            width = 0.3,
            level = 0,
            animation = true,
            bg = famBags:GetBGDefaults(),
         },
         alts = {
            hpadding = 0,
            vpadding = 0,
            width = 0.3,
            level = 0,
            animation = true,
            bg = famBags:GetBGDefaults(),
         },
      },
      bankwindow = {
         enabled = true,
         bg = famBags:GetBGDefaults(),
      },
      bagswindow = {
         enabled = true,
         bg = famBags:GetBGDefaults(),
      },
      sections = {
         bags= {
            bg = famBags:GetBGDefaults(),
            title = famBags:GetTitleDefaults(),
         },
      },
      categoriesV2 = {
      },
      minimized_sections = {
         bags = {},
         bank = {},
      },
   },
}

-- :::::::::::: LOCALS ::::::::::::: -------------------------------------------------------------------------

local function npairs(t)
   local a = {}
      for n in pairs(t) do table.insert(a, n) end
      table.sort(a)
      local i = 0      -- iterator variable
      local iter = function ()   -- iterator function
         i = i + 1
         if a[i] == nil then return nil
         else return a[i], t[a[i]]
         end
      end
   return iter
end

local function get_tooltip_text(label, value, type)
   if type then
      if type == 'bool' then
         value = (value) and 'Yes' or 'No'
      elseif type == 'global' then
         value = (_G[value]) and _G[value] or '--'
      end
   else
   value = value and value or '--'
   end
   return '|cffc5affe'..label..':|r '..value
end

local function get_value_index(arr, value, field)
   for i,v in pairs(arr) do
      if field then
         if v[field] == value then return i end
      else
         if v == value then return i end
      end
   end
   return nil
end


local LOADING = true
local UPDATE_COOLDOWNS = false
local BAGS_CHANGED = {}
local BAGS_CHANGED_COUNT = 0
-- :::::::::::: famBags ::::::::::::: -------------------------------------------------------------------------

function famBags:BuildSortedCategories()

 --   self.SortedCategories = self.SortedCategories or {}
 --   wipe(self.SortedCategories)
 --   for n in npairs(db.categories) do
 --      table.insert(self.SortedCategories, n)
 --   end
 --   famBags.Criterias[L['Categories']].options = self.SortedCategories

   self.SortedCategories = self.SortedCategories or {
      ['all'] = {},
      ['bags'] = {},
      ['bank'] = {},
      ['byName'] = {},
   }
   wipe(self.SortedCategories.all)
   wipe(self.SortedCategories.bags)
   wipe(self.SortedCategories.bank)
   wipe(self.SortedCategories.byName)
   for i, c in ipairs(db.categoriesV2) do
      table.insert(self.SortedCategories.all, c.category)
      self.SortedCategories.byName[c.category] = i
      if c.container == 'all' or c.container == 'bags' then
         table.insert(self.SortedCategories.bags, c.category)
      end
      if c.container == 'all' or c.container == 'bank' then
         table.insert(self.SortedCategories.bank, c.category)
      end
   end
   famBags.Criterias[FAMBAGSCRITERIA_CATEGORIES].options = self.SortedCategories.all -- Categories
end

function famBags:BuildBagIndexes()
   self.BagIndexes = {
      bags = {},
      bank = {}
   }
   local dbb = self.dbButtons[self.PLAYER_NAME]

   for i = 0, NUM_BAG_SLOTS do
      table.insert(self.BagIndexes.bags, i)
      if self.OFFLINE_ENABLED then
         dbb[i] = dbb[i] or {}
      end
   end
   if db.layout.general.show_keys then
      --table.insert(self.BagIndexes.bags, KEYRING_CONTAINER)
      if self.OFFLINE_ENABLED then
         dbb[KEYRING_CONTAINER] = dbb[KEYRING_CONTAINER] or {}
      end
   end
   table.insert(self.BagIndexes.bank, BANK_CONTAINER)
   if self.OFFLINE_ENABLED then
      dbb[BANK_CONTAINER] = dbb[BANK_CONTAINER] or {}
   end
   for i = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do
      table.insert(self.BagIndexes.bank, i)
      if self.OFFLINE_ENABLED then
         dbb[i] = dbb[i] or {}
      end
   end
end

function famBags:BuildInventory()
   wipe(self.Inventory)
   local id, count
   for _, bag in ipairs(self.BagIndexes.bags) do
      local slots = GetContainerNumSlots(bag)
      if slots then
         for slot = 1, slots do
            id = GetContainerItemID(bag, slot)
            if id then
               _, count = GetContainerItemInfo(bag, slot)
               if self.Inventory[id] then
                  self.Inventory[id] = self.Inventory[id] + (count or 0)
               else
                  self.Inventory[id] = count
               end
            end
         end
      end
   end
   for i = 0, 23 do
      id = GetInventoryItemID('player', i)
      if id then
         if self.Inventory[id] then
            self.Inventory[id] = self.Inventory[id] + 1
         else
            self.Inventory[id] = 1
         end
      end
   end
end

function famBags:GetInventoryForItem(itemID)
   if not itemID then return 0 end
   local id, count, b
   local total = 0
   for _, bag in ipairs(self.BagIndexes.bags) do
      local slots = GetContainerNumSlots(bag)
      if slots then
         for slot = 1, slots do
            id = GetContainerItemID(bag, slot)
            b = nil
            if id == itemID then
               if self.Buttons[bag] then
                  b = self.Buttons[bag][slot]
                  if b then
                     if b.info.new then
                        return 'new'
                     elseif b.info.added then
                        return 'added'
                     end
                  end
               end
               _, count = GetContainerItemInfo(bag, slot)
               total = total + (count or 0)
            end
         end
      end
   end
   return total
end

function famBags:GetDBDefaults()
   return defaults
end

function famBags:OnInitialize(...)
   self.db = LibStub('AceDB-3.0'):New('famBags', defaults, 'Default')
   db = self.db.profile
   if not self.db.global.offline.enabled then
      wipe(self.db.global.offline.buttons)
   end
   if db.categories then
      wipe(db.categoriesV2)
      local c = famBags.Compatibility:ConvertCategories()
      if c and c > 0 then
         print('|cffff3333famBags|cffffff00:|r  '..c..' categories converted to|cff55FF55 famBagsClassification v2.0')
      end
      db.categories = nil
   end
   self:InitLayoutVars()
   self.isStackMode = db.stackmode
   self.isSearchMode = db.searchmode
   self:PrepareForOffline()
   self.MoveToCategoryDD = CreateFrame("Frame", "famBagsMoveToCategoryDD", UIParent, "UIDropDownMenuTemplate");
   UIDropDownMenu_Initialize(self.MoveToCategoryDD, self.InitMoveToCategoryMenu, "MENU");
   self.ItemActionsDD = CreateFrame("Frame", "famBagsItemActionsDD", UIParent, "UIDropDownMenuTemplate");
   UIDropDownMenu_Initialize(self.ItemActionsDD, self.InitItemActionsMenu, "MENU");
   self:RegisterChatCommand('fam', 'ChatCommand')
   self:RegisterChatCommand('fambags', 'ChatCommand')
   self:RawHook("ToggleAllBags", "ToggleBags", true)
   self:RawHook("OpenAllBags", "ToggleBags", true)
   self:RawHook("OpenBackpack", "ShowBags", true)
   --self:RawHook("CloseBackpack", "HideBags", true)
   self:RawHook("CloseSpecialWindows", "HideBagsOnEsc", true)
   self:RawHook("ContainerFrameItemButton_OnEnter", "ContainerFrameItemButtonOnEnter", true)
   self:RawHook("ContainerFrameItemButton_OnModifiedClick", "ContainerFrameItemButtonOnModifiedClick", true)
   self:RawHook("SetCurrencyBackpack", "SetCurrencyBackpack", true)
   self:RawHookScript(BankFrame, "OnEvent", "BankFrameOnEvent")
end

function famBags:PrepareForOffline()
   self.OFFLINE_ENABLED = self.db.global.offline.enabled
   self.PLAYER_NAME = self.PLAYER_NAME or UnitName('player')..' ('..GetRealmName()..')'
   self.CURRENT_PLAYER['bags'] = self.PLAYER_NAME
   self.CURRENT_PLAYER['bank'] = self.PLAYER_NAME
   self.dbButtons = self.db.global.offline.buttons
   if self.OFFLINE_ENABLED then
      self.dbButtons[self.PLAYER_NAME] = self.dbButtons[self.PLAYER_NAME] or {}
      self.dbButtons[self.PLAYER_NAME].money = self.dbButtons[self.PLAYER_NAME].money or {}
      for n in pairs(self.dbButtons) do
         if n ~= self.PLAYER_NAME then
            self.CHARACTERS[n] = n
         end
      end
   end
end

function famBags:OnEnable()
   self:RegisterEvent('PLAYER_ENTERING_WORLD')
   self:RegisterEvent('ITEM_LOCK_CHANGED')
   self:RegisterEvent('BAG_UPDATE_COOLDOWN')
   self:RegisterEvent('EQUIPMENT_SETS_CHANGED')
   self:RegisterEvent("BANKFRAME_OPENED")
   self:RegisterEvent("BANKFRAME_CLOSED")
   self:RegisterEvent("PLAYER_MONEY")
   self:RegisterEvent('AUCTION_HOUSE_SHOW')
   self:RegisterEvent('AUCTION_HOUSE_CLOSED')
   self:RegisterEvent("CURRENCY_DISPLAY_UPDATE");
   self:RegisterEvent("KNOWN_CURRENCY_TYPES_UPDATE");
end

function famBags:ChatCommand(input)
   if not input or input:trim() == '' then
      self:LoadOptions()
   end
end

function famBags:LoadOptions()
   if LoadAddOn('famBagsOptions') then
      InterfaceOptionsFrame_OpenToCategory('famBags')
   end
end

function famBags:FullRefresh()
   local function wipe_section(sections, wipeit)
      if not sections then return end
      for _, s in pairs(sections) do
         s:Hide()
      end
      if wipeit then
         wipe(sections)
      end
   end

   CloseBankFrame()
   CloseBackpack()
   db = self.db.profile
   self:InitLayoutVars()
   for _, indexes in pairs(self.BagIndexes) do
      for _, i in ipairs(indexes) do
         if self.Buttons[i] then
            for _, b in ipairs(self.Buttons[i]) do
               b:Hide()
            end
         end
      end
   end
   wipe(self.Buttons.Current)
   if self.Sections.bags then
      wipe_section(self.Sections.bags.bags)
      wipe_section(self.Sections.bags.categories, true)
   end
   if self.Sections.bank then
      wipe_section(self.Sections.bank.bags)
      wipe_section(self.Sections.bank.categories, true)
   end
   self.famBagsMoneyFrame = nil
   self.famBagsBlizzardBankFrame = nil
   self.famBagsBagSlotsFrame = nil
   self.CHANGING_PROFILE = true
   self.EMPTIES_NEED_LAYOUT['bags'] = true
   self.EMPTIES_NEED_LAYOUT['bank'] = true
   LOADING = true
   self:PLAYER_ENTERING_WORLD()
end

function famBags:BankFrameOnEvent(frame, event)
   if (not db.bankwindow.enabled) or (event ~= 'BANKFRAME_OPENED' and event ~= 'BANKFRAME_CLOSED') then
      self.hooks[BankFrame].OnEvent(frame, event)
   end
end

function famBags:ContainerFrameItemButtonOnEnter(b)
   if not b.info then
      return self.hooks.ContainerFrameItemButton_OnEnter(b)
   end
   if not b.info.id then return end
   local bag = b:GetParent():GetID()
   local frame = (tContains(famBags.BagIndexes.bags, bag)) and 'bags' or 'bank'
   if not famBags.MAINFRAMES[frame].offline then
      if bag == BANK_CONTAINER then
         -- Bank main container specific code
         local x = b:GetRight()
         GameTooltip:SetOwner(b, (x >= ( GetScreenWidth() / 2)) and "ANCHOR_LEFT" or "ANCHOR_RIGHT")
         GameTooltip:SetInventoryItem("player", BankButtonIDToInvSlotID(b:GetID()))
         CursorUpdate(b)
      else
         self.hooks.ContainerFrameItemButton_OnEnter(b)
      end
   else
      local x = b:GetRight()
      GameTooltip:SetOwner(b, (x >= ( GetScreenWidth() / 2)) and "ANCHOR_LEFT" or "ANCHOR_RIGHT")
      GameTooltip:SetHyperlink(b.info.link)
      GameTooltip:Show()
   end
   if db.layout.general.info then
      GameTooltip:AddLine('|cff885cfc--=== |cffffff53famBags Item Info|cff885cfc ===----------------', 1, 1, 1)
      GameTooltip:AddLine(get_tooltip_text('ID', b.info.id), 1, 1, 1)
      GameTooltip:AddDoubleLine(get_tooltip_text('Type', b.info.type), get_tooltip_text('SubType', b.info.subType), 1, 1, 1, 1, 1, 1)
      GameTooltip:AddDoubleLine(get_tooltip_text('Item level', b.info.iLvl), get_tooltip_text('Required level', b.info.minLvl), 1, 1, 1, 1, 1, 1)
      GameTooltip:AddDoubleLine(get_tooltip_text('Spell', b.info.spell), get_tooltip_text('Items x stack', b.info.stack), 1, 1, 1, 1, 1, 1)
      GameTooltip:AddDoubleLine(get_tooltip_text('Quest item', b.info.isQuestItem, 'bool'), get_tooltip_text('Quest ID', b.info.questID), 1, 1, 1, 1, 1, 1)
      GameTooltip:AddDoubleLine(get_tooltip_text('Readable', b.info.readable, 'bool'), get_tooltip_text('Lootable', b.info.lootable, 'bool'), 1, 1, 1, 1, 1, 1)
      local default_set, outfitter_set
      if b.info.id then
         for set, items in pairs(famBags.EquipmentSets) do
            if items and rawget(items, b.info.id) then
               default_set = (default_set) and default_set..', '..set or set
            end
         end
      end
      if famBags.OUTFITTER then
         for set, items in pairs(famBags.OutfitterSets) do
            if items and rawget(items, b.info.id) then
               outfitter_set = (outfitter_set) and outfitter_set..', '..set or set
            end
         end
      end
      GameTooltip:AddDoubleLine(get_tooltip_text('Equipment set', default_set), get_tooltip_text('Outfitter set', outfitter_set), 1, 1, 1, 1, 1, 1)
      GameTooltip:Show()
   end
end

function famBags:ContainerFrameItemButtonOnModifiedClick(b, mousebutton)
 --   if not b.info.id then return end
 --   local bag = b:GetParent():GetID()
 --   local frame = (tContains(famBags.BagIndexes.bags, bag)) and 'bags' or 'bank'
 --   if self.MAINFRAMES[frame].offline then return end
   if IsControlKeyDown() and mousebutton == 'RightButton' and b.info and b.info.id then
      self.MoveToCategoryDD.button = b
      ToggleDropDownMenu(1, nil, self.MoveToCategoryDD, b:GetName(), b:GetWidth()/2, b:GetHeight()/2)
   elseif IsAltKeyDown() and mousebutton == 'RightButton' and b.info and b.info.id then
      self.ItemActionsDD.button = b
      ToggleDropDownMenu(1, nil, self.ItemActionsDD, b:GetName(), b:GetWidth()/2, b:GetHeight()/2)
   else
      self.hooks.ContainerFrameItemButton_OnModifiedClick(b, mousebutton)
   end
end

function famBags:SaveSkin(index, skinID, gloss, backdrop, colors)
   db.layout.skins[index].skinID = skinID
   db.layout.skins[index].gloss = gloss
   db.layout.skins[index].backdrop = backdrop
   db.layout.skins[index].colors = colors
end

function famBags:BFSkinCallback(skinID, gloss, backdrop, group, button, colors)
   if not group then
      self:SaveSkin('bags', skinID, gloss, backdrop, colors)
   elseif group == 'Bags' then
      self:SaveSkin('bags', skinID, gloss, backdrop, colors)
   end
   --hack to fix texture size for keyring button
   if self.famBagsBagSlotsFrame and self.famBagsBagSlotsFrame.KeyRingTexture then
      self.famBagsBagSlotsFrame.KeyRingTexture:SetTexCoord(0, 0.5625, 0, 0.609375)
   end
end

function famBags:PLAYER_ENTERING_WORLD()
   if not LOADING then return end
   LOADING = nil
   if not self.CHANGING_PROFILE then
      self:BuildBagIndexes()
      self:EQUIPMENT_SETS_CHANGED()
      self.LDBBags:Create()
      self.LDBCurrency:Create()
   end
   if lbf then
      self.BFBags = lbf:Group("famBags", "Bags")
      self.BFBags:Skin(db.layout.skins.bags.skinID, db.layout.skins.bags.gloss, db.layout.skins.bags.backdrop, db.layout.skins.bags.colors)
      lbf:RegisterSkinCallback("famBags", self.BFSkinCallback, self)
   end
   self:BuildSortedCategories()
   self:UpdateBags()
   self:CreateIntefaceButtons()
   if db.bagswindow.enabled and self.BagSlotsButton then
      self.BagSlotsButton:SetChecked(false)
   end
   if db.bankwindow.enabled and self.BankSlotsButton then
      self.BankSlotsButton:SetChecked(false)
   end
   if not self.CHANGING_PROFILE then
      self:RegisterEvent('BAG_UPDATE')
      self:RegisterEvent('PLAYERBANKSLOTS_CHANGED')
      self:RegisterEvent('PLAYERBANKBAGSLOTS_CHANGED')
      if Outfitter then
         if Outfitter:IsInitialized() then
            self.OUTFITTER = true
            Outfitter:RegisterOutfitEvent('EDIT_OUTFIT', self.OUTFITTER_SETS_CHANGED)
            Outfitter:RegisterOutfitEvent('ADD_OUTFIT', self.OUTFITTER_SETS_CHANGED)
            Outfitter:RegisterOutfitEvent('DELETE_OUTFIT', self.OUTFITTER_SETS_CHANGED)
            self:OUTFITTER_SETS_CHANGED()
         else
            Outfitter:RegisterOutfitEvent('OUTFITTER_INIT', self.OUTFITTER_INIT)
         end
      end
   end
end

function famBags:ITEM_LOCK_CHANGED(e, bag, slot)
   if slot and self.Buttons[bag] and not (bag == BANK_CONTAINER and slot > NUM_BANKGENERIC_SLOTS) then
      local b = self.Buttons[bag][slot]
      self.Buttons:UpdateItemLock(b, bag, slot)
      self.Buttons:UpdateLock(b)
   end
end

function famBags:BAG_UPDATE_COOLDOWN(e, bag)
   UPDATE_COOLDOWNS = true
   self.ticktime = 0.1
   self:StartTimer()
end

function famBags:PLAYERBANKSLOTS_CHANGED(e, slot)
   if slot <= NUM_BANKGENERIC_SLOTS then
      self:BAG_UPDATE(e, BANK_CONTAINER)
   else
      self:BAG_UPDATE(e, NUM_BAG_SLOTS + slot - NUM_BANKGENERIC_SLOTS)
      if db.bankwindow.enabled then
         BankFrameItemButton_Update(self.famBagsBlizzardBankFrame.BankBags[slot - NUM_BANKGENERIC_SLOTS])
      end
   end
end

function famBags:PLAYERBANKBAGSLOTS_CHANGED()
   if db.bankwindow.enabled then
      UpdateBagSlotStatus()
   end
   local _, full = GetNumBankSlots()
   local f = self.famBagsBlizzardBankFrame
   if full and db.bankwindow.enabled and f and f.PurchaseInfo then
      f.PurchaseInfo:ClearAllPoints()
      f.PurchaseInfo:Hide()
      f:SetHeight(f.BankBags[1]:GetHeight() + 20)
   end
end

function famBags:BAG_UPDATE(e, bag)
   if bag == KEYRING_CONTAINER and not db.layout.general.show_keys then return end
   local found
   for i=1, BAGS_CHANGED_COUNT do
      if BAGS_CHANGED[i] == bag then
         found = true
         break
      end
   end
   if not (found) then
      BAGS_CHANGED_COUNT = BAGS_CHANGED_COUNT + 1
      BAGS_CHANGED[BAGS_CHANGED_COUNT] = bag
   end
   if not self.OPTIMIZATION.inProgress then
      self.ticktime = db.layout.general.update_time
      self:StartTimer()
   end
end

function famBags:EQUIPMENT_SETS_CHANGED()
   if self.EquipmentSets then
      wipe(self.EquipmentSets)
   else
      self.EquipmentSets = {}
   end
   wipe(self.Criterias[FAMBAGSCRITERIA_EQUIPSET].options)
   local setscount = GetNumEquipmentSets()
   for i=1, setscount do
      local set = GetEquipmentSetInfo(i);
      local items = GetEquipmentSetItemIDs(set);
      self.EquipmentSets[set] = {}
      table.insert(self.Criterias[FAMBAGSCRITERIA_EQUIPSET].options, set)
      for _, id in pairs(items) do
         self.EquipmentSets[set][id] = true
      end
   end
   if self.famBagsFrame then
      if IsAddOnLoaded('famBagsOptions') then
         LibStub('AceConfigRegistry-3.0'):NotifyChange('famBags Categories')
      end
      self:LayoutBagsButtons()
   end
end

function famBags:OUTFITTER_INIT()
   if Outfitter:IsInitialized() then
      famBags.OUTFITTER = true
      Outfitter:RegisterOutfitEvent('EDIT_OUTFIT', famBags.OUTFITTER_SETS_CHANGED)
      Outfitter:RegisterOutfitEvent('ADD_OUTFIT', famBags.OUTFITTER_SETS_CHANGED)
      Outfitter:RegisterOutfitEvent('DELETE_OUTFIT', famBags.OUTFITTER_SETS_CHANGED)
      famBags:OUTFITTER_SETS_CHANGED()
   end
end

function famBags:OUTFITTER_SETS_CHANGED()
   if self.OutfitterSets then
      wipe(famBags.OutfitterSets)
   else
      famBags.OutfitterSets = {}
   end
   wipe(famBags.Criterias[FAMBAGSCRITERIA_OUTFITTERSET].options)

   for _, c in ipairs(Outfitter:GetCategoryOrder()) do
      for _, o in ipairs(Outfitter:GetOutfitsByCategoryID(c)) do
         if not o:IsEmpty() then
            local set = o:GetName()
            famBags.OutfitterSets[set] = {}
            table.insert(famBags.Criterias[FAMBAGSCRITERIA_OUTFITTERSET].options, set)
            for _, info in pairs(o.Items) do
               famBags.OutfitterSets[set][info.Code] = true
            end
         end
      end
   end
   --dump('famBags.OutfitterSets',famBags.OutfitterSets)
   if famBags.famBagsFrame then
      if IsAddOnLoaded('famBagsOptions') then
         LibStub('AceConfigRegistry-3.0'):NotifyChange('famBags Categories')
      end
      if famBags.BAGS_LOADED then
         famBags:LayoutBagsButtons()
      end
   end
end

function famBags:BANKFRAME_OPENED()
   self.famBankFrame:Show()
   self.IsBankOpen = true
   local layoutdone
   if self.OFFLINE_ENABLED then
      self:ForceCurrentPlayer('bank')
      self:ForceCurrentPlayer('bags')
      self.BagsAltsButton:Disable()
      self.BankAltsButton:Disable()
      layoutdone = true
   end
   if db.bankwindow.enabled then
      for i=1, NUM_BANKBAGSLOTS, 1 do
         button = _G["BankFrameBag"..i];
         BankFrameItemButton_Update(button);
      end
      UpdateBagSlotStatus();
      --self.famBagsBlizzardBankFrame:Show()
   end
   if not layoutdone then
      self:LayoutBagsButtons('bank')
   end
   self:ShowBags()
end

function famBags:OpenBankOffline()
   if not famBags.OFFLINE_ENABLED or famBags.famBankFrame:IsVisible() then return end
   if not famBags.famBankFrame.offline then
      famBags:SetTextLayout(famBags.famBankFrame.titleFrame, famBags.famBankFrame.titleFrame.titleText, db.bank.title.text, L['Bank']..' ['..L['offline']..']')
   end
   famBags.famBankFrame.offline = true
   self:ToggleBagSlotsButtonStatus('bank')
   self:ToggleOptimizeButtonStatus('bank')
   self:ToggleRefreshButtonStatus('bank')
   famBags.famBankFrame:Show()
   famBags:LayoutBagsButtons('bank')
end

function famBags:BANKFRAME_CLOSED()
   self.IsBankOpen = nil
   if self.famBankFrame and self.famBankFrame:IsVisible() then
      self.famBankFrame:Hide()
      self:HideBags()
   end
   if self.OFFLINE_ENABLED and self.BagsAltsButton and self.BankAltsButton then
      self.BagsAltsButton:Enable()
      self.BankAltsButton:Enable()
   end
end

function famBags:AUCTION_HOUSE_SHOW()
   self:ShowBags()
end

function famBags:AUCTION_HOUSE_CLOSED()
   self:HideBags()
end

function famBags:PLAYER_MONEY()
   self:UpdateCurrency()
end

function famBags:SetCurrencyBackpack(...)
   self.hooks.SetCurrencyBackpack(...)
   self:UpdateCurrency()
end

function famBags:CURRENCY_DISPLAY_UPDATE()
   self:UpdateCurrency()
end

function famBags:KNOWN_CURRENCY_TYPES_UPDATE()
   self:UpdateCurrency()
end

function famBags:UpdateCurrency()
   local money = self:GetMoney()
   if self.famBagsMoneyFrame and not self.famBagsFrame.offline then
      self:SetTextLayout(self.famBagsMoneyFrame, 'moneyText', db.money.text, money, true, false)
   end
   self.LDBCurrency:Update(money)
end

function famBags:UpdateOfflineCurrency()
   if not self.famBagsMoneyFrame then return end
   local money = self:GetOfflineMoney()
   self:SetTextLayout(self.famBagsMoneyFrame, 'moneyText', db.money.text, money, true, false)
end

function famBags:StartTimer()
   self.timer = 0
   self.timerFrame:Show()
end

function famBags:StopTimer()
   self.timerFrame:Hide()
end

function famBags:TimerTick()
   self:StopTimer()
   if self.OPTIMIZATION.inProgress then
      self:OptimizeStacks(self.OPTIMIZATION.bag)
      return
   end
   if UPDATE_COOLDOWNS then
      self.Buttons:UpdateButtonsCooldown()
      UPDATE_COOLDOWNS = false
   end
   if self.FIRST_BUILD then
      self:UpdateAllButtons('bags')
      self.FIRST_BUILD = false
   end
   if BAGS_CHANGED_COUNT > 0 then
      self:UpdateChangedBags()
   end
end

function famBags:ShowBags()
   if not self.famBagsFrame:IsVisible() then
      self.famBagsFrame:Show()
      self:LayoutBagsButtons('bags')
      PlaySound("igBackPackOpen")
   end
end

function famBags:HideBagsOnEsc()
   if self.famBagsFrame and self.famBagsFrame:IsVisible() then
      self:HideBags()
      CloseBankFrame()
      return self.hooks.CloseSpecialWindows() or 1
   elseif self.famBankFrame and self.famBankFrame.offline and self.famBankFrame:IsVisible() then
      CloseBankFrame()
      return self.hooks.CloseSpecialWindows() or 1
   end
   CloseBankFrame()
   return self.hooks.CloseSpecialWindows()
end

function famBags:HideBags()
   if self.famBagsFrame and self.famBagsFrame:IsVisible() then
      self.famBagsFrame:Hide()
      PlaySound("igBackPackClose")
   end
end

function famBags:ToggleBags(force)
   if not self.famBagsFrame then return end
   if (not self.famBagsFrame:IsVisible()) or force then
      self.famBagsFrame:Show()
      self:LayoutBagsButtons('bags')
      PlaySound("igBackPackOpen")
   else
      self:HideBags()
   end
end

function famBags:UpdateChangedBags()
   self.LDBBags:Update()
   if not self.famBagsFrame then
      self:BuildInventory()
      BAGS_CHANGED_COUNT = 0
      return
   end
   if self.OFFLINE_ENABLED and self.CURRENT_PLAYER['bags'] ~= self.PLAYER_NAME then
      self:ForceCurrentPlayer('bags')
   end
   local i
   local updatebags
   local updatebank
   local bagchanged
   self.BAGS_NEED_LAYOUT = nil
   self.BANK_NEED_LAYOUT = nil
   for k = 1, BAGS_CHANGED_COUNT do
      i = BAGS_CHANGED[k]
      bagchanged = nil
      local slots = GetContainerNumSlots(i)
      local bagType = select(2, GetContainerNumFreeSlots(i))
      if self.Buttons and self.Buttons[i] then
         if i ~= KEYRING_CONTAINER and bagType ~= self.Buttons[i].bagType then
            bagchanged = (tContains(self.BagIndexes.bags, i)) and 'bags' or 'bank'
            self.Buttons[i].bagType = bagType
            self.EMPTIES_NEED_LAYOUT[bagchanged] = true
         end
         if #self.Buttons[i] > slots then
            for j = slots + 1, #self.Buttons[i] do
               self.Buttons[i][j]:Hide()
            end
         end
      end
      self:UpdateBagButtons(i, slots, bagchanged)
      if tContains(self.BagIndexes.bags, i) then
         updatebags = true
      elseif tContains(self.BagIndexes.bank, i) then
         updatebank = true
      end
   end
   BAGS_CHANGED_COUNT = 0
   if updatebags then
      if db.layout.general.full_layout or self.BAGS_NEED_LAYOUT or not self.Buttons.Current['bags'] then
         self:LayoutBagsButtons('bags')
      else
         self:LightLayoutBagsButtons('bags')
      end
      self:BuildInventory()
   end
   if updatebank then
      if db.layout.general.full_layout or self.BANK_NEED_LAYOUT or not self.Buttons.Current['bank'] then
         self:LayoutBagsButtons('bank')
      else
         self:LightLayoutBagsButtons('bank')
      end
   end
end

function famBags:MoveToCategory(category, id)
   if id then
      local index
      local c = db.categoriesV2
      for catindex, cat in ipairs(famBags.SortedCategories.all) do
         if c[catindex].Items and c[catindex].Items[id] then
            c[catindex].Items[id] = nil
            break
         end
      end
      if category ~= 'clear' then
         if category then
            c[category].Items = c[category].Items or {}
            c[category].Items[id] = 1
         end
      end
      famBags:LayoutBagsButtons()
   end
end

function famBags:UseSectionItems()
   if not (self.SellingSection and self.SellingSectionBag and
           self.Buttons.Current[self.SellingSectionBag] and
           self.Buttons.Current[self.SellingSectionBag][self.SellingSection]) then
      return
   end
   for _,b in ipairs(self.Buttons.Current[self.SellingSectionBag][self.SellingSection]) do
      if b.info.id then
         for _,b1 in ipairs(b.stacks) do
            if b1.info.id then
               ContainerFrameItemButton_OnClick(b1, 'RightButton')
            end
         end
         ContainerFrameItemButton_OnClick(b, 'RightButton')
      end
   end
end

function famBags:ForceCurrentPlayer(bag)
   self.ForceCurrentPlayerChange[bag] = true
   local db = bag == 'bags' and famBags.db.profile.mainwindow.alts or famBags.db.profile.bank.alts
   local b = bag == 'bags' and self.BagsAltsButton or self.BankAltsButton
   local anim = db.animation
   db.animation = false
   if b:GetChecked() then
      b:Click()
   else
      famBags:ChangeCurrentPlayer(self.PLAYER_NAME, bag)
   end
   db.animation = anim
end

function famBags:CopyTable(t)
  local u = {}
  for k,v in pairs(t) do
     u[k] = v
  end
  return setmetatable(u, getmetatable(t))
end


   --[[
function famBags:OnDisable()
end

SLASH_FAMBAGS1 = '/fambags'
SlashCmdList['FAMBAGS'] = function(msg, editbox)
end
--]]

--v010207
--Weapon Armor Container Consumable Glyph Trade Goods Recipe Gem Miscellaneous Quest Battle Pets
--One-Handed Axes Two-Handed Axes Bows Guns One-Handed Maces Two-Handed Maces Polearms One-Handed Swords Two-Handed Swords Staves Fist Weapons Miscellaneous Daggers Thrown Crossbows Wands Fishing Poles
--Miscellaneous Cloth Leather Mail Plate Shields Relic
--Bag Herb Bag Enchanting Bag Engineering Bag Gem Bag Mining Bag Leatherworking Bag Inscription Bag Tackle Box
--Food & Drink Potion Elixir Flask Bandage Item Enhancement Scroll Other
--Warrior Paladin Hunter Rogue Priest Death Knight Shaman Mage Warlock Monk Druid
--Elemental Cloth Leather Metal & Stone Cooking Herb Enchanting Jewelcrafting Parts Devices Explosives Materials Other Item Enchantment
--Book Leatherworking Tailoring Engineering Blacksmithing Cooking Alchemy First Aid Enchanting Fishing Jewelcrafting Inscription
--Red Blue Yellow Purple Green Orange Meta Simple Prismatic Cogwheel
--Junk Reagent Companion Pets Holiday Other Mount
--Humanoid Dragonkin Flying Undead Critter Magic Elemental Beast Aquatic Mechanical